<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------

declare (strict_types = 1); //声明代码的执行指令为开启严格模式,主要为参数类型为严格模式,方便常驻内存

namespace think;

use think\event\AppInit;
use think\helper\Str;
use think\initializer\BootService;
use think\initializer\Error;
use think\initializer\RegisterService;


// +----------------------------------------------------------------------
// |
// | (愿景,目的) 所属:基础类
// | (功能) 名称: App 基础类
// | (心智)原则:单一职责原则/里氏替换原则/依赖倒置原则/接口隔离原则/迪米特法则/开闭原则
// | (结构/形式) 类模式: 抽象类/多重继承
// | (模式)模式类型: 结构类/创造类/行为类/
// | (模式)设计模式:
// | 产生事件:
// | Understand:Cluster | Cluster Call Butterfly
// |
// | ---------分析--------------
// | 需求的产生 想法
// | 道: <用户-客户立场>|<人的视角>(欲望)
// | 愿景: 框架的入口引导协调基础
// | 目的: 框架核心基础,规范架构协调,响应请求事件的初始和销毁相关处理
// |
// | =============================
// | 需求的分解--架构-偏商业架构/业务角度
// | 制定设置规则的原则
// | 法: <组织内部立场>|<人的视角>
// | 如何适应新的应用场景
// | 适用场景:cli/web_http/api_http/fpm
// | 目的:
// | 心智:遵循思维模式,原则
// | (系统)功能: https://i.loli.net/2020/01/16/fSGYvDw8Ln9dMqa.png
// | 结构:
// | 协议:
// |
// | =============================
// | 不同环境下的适应性,范围越大越优雅,TP 在复杂应用环境下对领域边界的处理不够,
// | 照成项目文件的复杂错乱,不易迭代,是导致被鄙视的根源
// | 需求点规划/-架构-技术架构,开发上这块是核心 做法
// | 制定可执行的规则
// | 对什么场景下的哪个业务节点做什么结构,和形式设定,以达到什么目的
// | 术:<组织内部立场>|<物的视角>
// | 用户/客户立场:(人的视角)(结果)感知
// | 形式:定义了整个项目的文件目录形式,各目录文件功能
// |
// | 组织内部立场:(物的视角)(设定)系统思考.逻辑思考
// | 结构:
// |
// | 协议:
// |
// | =============================
// | 方案实施 执行 怎么用
// | 器:<物的视角>
// | 对什么场景下的哪个业务节点.有什么资源方法和模式可用
// | (转换接口,把物和人联系起来)
// | 形式:
// |
// | (用户/客户立场|视角|(背后是结构)
// | 模式:
// |
// | (具体方法)
// | 事件:
// | -------------------------
// |
// | 知识点:
// | 继承类说明:
// | 点评: 核心类,项目的引导,继承了容器类
// |      TP6 核心改变是,项目进入容器编程模式
// |      从个人需求上说,TP 不能满足我的需求,对很多内容规定的过于刻板,如果要调整,需要对多处变量参数进行重新规划
// |      遵从 TP 的规范思想但不认同,TP 的对 APP 的开发理念.需要重新架构框架的形式结构
// |      TP 开发思想是轻量级,充分发挥 PHP 现阶段的大前端后台的定位,API 开发,这个和我的理念是不同的.
// |      我的开发理念是快速响应需求,代码和逻辑复用率的最大化.同时保持各个方法在长期多人协作下的清晰化,目录,文件的充分解耦
// |      为和官方保持同步有必要建立一键更新脚本,方便更新框架
// |
// +----------------------------------------------------------------------


/**
 * App 基础类
 * @property Route      $route
 * @property Config     $config
 * @property Cache      $cache
 * @property Request    $request
 * @property Http       $http
 * @property Console    $console
 * @property Env        $env
 * @property Event      $event
 * @property Middleware $middleware
 * @property Log        $log
 * @property Lang       $lang
 * @property Db         $db
 * @property Cookie     $cookie
 * @property Session    $session
 * @property Validate   $validate
 * @property Filesystem $filesystem
 * @link https://www.processon.com/view/link/5e11d38fe4b0698ded2cef93 详情说明
 */
class App extends Container
{
    const VERSION = '6.0.4';

    /**
     * 应用调试模式
     * @var bool
     */
    protected $appDebug = false;

    /**
     * 应用开始时间
     * @var float
     */
    protected $beginTime;

    /**
     * 应用内存初始占用
     * @var integer
     */
    protected $beginMem;

    /**
     * 当前应用类库命名空间
     * @var string
     */
    protected $namespace = 'app';

    protected $module='';

    /**
     * 应用根目录/项目根目录
     * @var string
     */
    protected $rootPath = '';

    /**
     * 框架目录
     * @var string
     */
    protected $thinkPath = '';

    /**
     * 应用目录
     * @var string
     */
    protected $appPath = '';

    /**
     * Runtime目录
     * @var string
     */
    protected $runtimePath = '';

    /**
     * 路由定义目录
     * @var string
     */
    protected $routePath = '';

    /**
     * 配置后缀
     * @var string
     */
    protected $configExt = '.php';

    /**
     * 应用初始化器
     * @var array
     * @depreacate 这个地方的变量加载时,初始加载了.进行哪些处理,个人感觉是加载了对应类的应用体.未做处理
     */
    protected $initializers = [
        Error::class,
        RegisterService::class,
        BootService::class,
    ];

    /**
     * 注册的系统服务
     * @var array
     */
    protected $services = [];

    /**
     * 初始化
     * @var bool
     */
    protected $initialized = false;

    /**
     * 容器绑定标识
     * @var array
     * @depreacate 这个地方的变量加载时,初始加载了.进行哪些处理,个人感觉是加载了对应类的应用体.未做处理
     * @depreacate 容器标识的本质是别名
     * @depreacate 注意这里绑定的只是类的标识,说白点是字符串,不是对象
     */
    protected $bind = [
        'app'                     => App::class,
        'cache'                   => Cache::class,
        'config'                  => Config::class,
        'console'                 => Console::class,
        'cookie'                  => Cookie::class,
        'db'                      => Db::class,
        'env'                     => Env::class,
        'event'                   => Event::class,
        'http'                    => Http::class,
        'lang'                    => Lang::class,
        'log'                     => Log::class,
        'middleware'              => Middleware::class,
        'request'                 => Request::class,
        'response'                => Response::class,
        'route'                   => Route::class,
        'session'                 => Session::class,
        'validate'                => Validate::class,
        'view'                    => View::class,
        'filesystem'              => Filesystem::class,
        // 这 3 个类标识要注意,为什么这么写,这 3 个类,在别的地方已经有引用,估计是为了方便管理.
        'think\DbManager'         => Db::class,
        'think\LogManager'        => Log::class,
        'think\CacheManager'      => Cache::class,

        // 接口依赖注入
        'Psr\Log\LoggerInterface' => Log::class,
    ];

    /**
     * 架构方法,初始时加载
     *
     * @access public
     * @param string $rootPath 应用根目录/项目根目录
     * @link https://gitee.com/kinghack001/app-manual-tp6/blob/master/doc/framework/app/__construct/readme.md 详情
     */
    public function __construct(string $rootPath = '')
    {
        // 框架目录
        $this->thinkPath   = dirname(__DIR__) . DIRECTORY_SEPARATOR;
        // 应用根目录/项目根目录
        $this->rootPath    = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
        // 应用目录
        // 这里引入了常量的概念,方便调整系统参数,不用常量,就是轻量级框架.有常量可以实现重型框架
        // {标识-前缀/}服务/应用/功能/{版本/}模块/方法
        // {标识-前缀/}服务/[应用(产品)/功能(部件)/]模块/方法 |  service/product/unit/class/fun
        // app/ examples/app/Request/Info/detailsParameter
        // app 代表产品的入口
        // 开发时,可以把不同功能模块加入到应用中,他们属于一个体系.
        // 也可以通过入口文件定义不同的产品.但可以共享部分公共模块
        // 其实这块的最大作用是应用的入口控制,方式上有仿 java 包->目录结构
        if(defined('APP_NAME')){
            $this->appPath     = $this->rootPath . APP_NAME . DIRECTORY_SEPARATOR;
            $this->namespace = APP_NAME; // 这一步可以配合入口文件进行定义,并不一定非要这样设置,但默认写,可以方便点.和优雅无关.
            // Runtime目录,对不同模式 cli swoole fpm需要不同的目录,避免编译混乱
            $this->runtimePath = defined('DATA_PATH')?$this->rootPath.DATA_PATH : $this->rootPath;
//            var_dump($this->runtimePath);exit;
            $this->runtimePath .=  'runtime'. DIRECTORY_SEPARATOR.APP_NAME .DIRECTORY_SEPARATOR;
            // 对不同访问场景做区分,方便找问题.主要针对单体应用
            if(IS_CLI){
                $this->runtimePath .= 'cli'. DIRECTORY_SEPARATOR;
            }elseif(IS_SWOOLE){
                $this->runtimePath .= 'swoole'. DIRECTORY_SEPARATOR;
            }else{
                $this->runtimePath .= 'fpm'. DIRECTORY_SEPARATOR;
            }
//            var_dump($this->runtimePath);exit;
        }else{
            $this->appPath     = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
            $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
        }

        // 需要明白容器是怎么回事,同时设置框架外部容器定义文件也是方便打造个性化应用的容器加载,避免和框架内核绑定过重
        // 查找容器定义文件是否存在
        if (is_file($this->appPath . 'provider.php')) {
            // 绑定一个类、闭包、实例、接口实现到容器
            // 加载容器Provider定义文件中的定义
            // 在容器绑定标识中增加新的类
            $this->bind(include $this->appPath . 'provider.php');
        }

        // 设置当前容器的实例,这个时候,实际就是一个空实例.里面都是默认值为空的字段
        // 说白点,就是建立一个静态类对象,可能是全局会用到的,算基类,只是这里建立的源对象,是当前运行的实例,运行到这里,就把前面的容器都绑定到当前容器了.
        static::setInstance($this);

        // 绑定一个类实例到容器,绑定自身
        // 这里开始绑定对象到容器了 ??? 多应用下可能存在问题
        $this->instance('app', $this);

        // 绑定一个类实例到容器
        // 往已经创建的实列再注入容器对象,这样会形成类似 swoole 的work 的效果,这是核心点,要注意这个问题
        $this->instance('think\Container', $this);

    }

    /**
     * 注册服务
     * @depreacate 作用:逻辑,调度
     * @depreacate 调用级别:2 有上级调用也有下级调用
     * @todo 这个服务有什么用?怎么用的.
     *       服务是类似于事件的存在,本质都是系统钩子,只是服务,是对功能的前置操作.事件基于标签的操作,可以放任意位置,因为这模式是类似的
     *
     * @access public
     * @param Service|string $service 服务
     * @param bool           $force   强制重新注册
     * @return Service|null
     */
    public function register($service, bool $force = false)
    {
        $registered = $this->getService($service);

        if ($registered && !$force) {
            return $registered;
        }

        if (is_string($service)) {
            $service = new $service($this);
        }

        if (method_exists($service, 'register')) {
            $service->register();
        }

        if (property_exists($service, 'bind')) {
            $this->bind($service->bind);
        }

        $this->services[] = $service;
    }

    /**
     * 执行服务
     * @depreacate 作用:逻辑
     * @depreacate 调用级别:3 有上级调用但无下级调用
     *
     * @access public
     * @param Service $service 服务
     * @return mixed
     */
    public function bootService($service)
    {
        if (method_exists($service, 'boot')) {
            // 调用反射执行callable
            return $this->invoke([$service, 'boot']);
        }
    }

    /**
     * 获取服务
     * @depreacate 作用:调度,逻辑
     * @depreacate 调用级别:3 有上级调用但无下级调用
     *
     * @param string|Service $service
     * @return Service|null
     */
    public function getService($service)
    {
        // 获取要取服务的类的名字
        $name = is_string($service) ? $service : get_class($service);
        // 用匿名函数,在服务数组中,把查找到的类.pos 出来
        return array_values(array_filter($this->services, function ($value) use ($name) {
            return $value instanceof $name;
        }, ARRAY_FILTER_USE_BOTH))[0] ?? null;
    }

    /**
     * 开启应用调试模式
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @access public
     * @param bool $debug 开启应用调试模式
     * @return $this
     */
    public function debug(bool $debug = true)
    {
        // 应用调试模式
        $this->appDebug = $debug;
        return $this;
    }

    /**
     * 是否为调试模式
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @access public
     * @return bool
     */
    public function isDebug(): bool
    {
        // 应用调试模式
        return $this->appDebug;
    }

    /**
     * 设置应用命名空间
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     * @todo 这个地方起始被限制死了.就是 app 在设置这个的命名空间其实没意义
     *
     * @access public
     * @param string $namespace 应用命名空间
     * @return $this
     */
    public function setNamespace(string $namespace)
    {
        // 当前应用类库命名空间
        $this->namespace = $namespace;
        return $this;
    }

    /**
     * 获取应用类库命名空间
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @access public
     * @return string
     */
    public function getNamespace(): string
    {
        // 当前应用类库命名空间
        return $this->namespace;
    }

    /**
     * 获取框架版本
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @access public
     * @return string
     */
    public function version(): string
    {
        return static::VERSION;
    }

    /**
     * 获取应用根目录/项目根目录
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @access public
     * @return string
     */
    public function getRootPath(): string
    {
        // 应用根目录/项目根目录
        return $this->rootPath;
    }

    /**
     * 获取应用基础目录
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     * @todo 注意这个地方,限制了应用的起始目录,如果要自定义起始目录.这个地方是需要处理的
     *
     * @access public
     * @return string
     */
    public function getBasePath(): string
    {
        // 应用根目录/项目根目录
        // 这个地方对多应用是不友好的,限定了路径
        if(defined('APP_NAME')){
            return $this->rootPath . APP_NAME . DIRECTORY_SEPARATOR;
        }else{
            return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
        }

    }

    /**
     * 获取当前应用目录
     * @depreacate 作用:数据
     *
     * @access public
     * @return string
     */
    public function getAppPath(): string
    {
        // 应用目录
        return $this->appPath;
    }

    /**
     * 设置应用目录
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @param string $path 应用目录
     */
    public function setAppPath(string $path)
    {
        // 应用目录
        $this->appPath = $path;
    }

    /**
     * 获取应用运行时目录
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @access public
     * @return string
     */
    public function getRuntimePath(): string
    {
        // Runtime目录
        return $this->runtimePath;
    }

    /**
     * 设置runtime目录
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @param string $path 定义目录
     */
    public function setRuntimePath(string $path): void
    {
        // Runtime目录
        $this->runtimePath = $path;
    }

    /**
     * 获取核心框架目录
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @access public
     * @return string
     */
    public function getThinkPath(): string
    {
        // 框架目录
        return $this->thinkPath;
    }

    /**
     * 获取应用配置目录
     * @depreacate 作用:数据
     * @todo 注意,这个地方其实解释的不准,不是应用根目录.是项目的根目录. 应用在 TP 指的是 APP 目录
     *
     * @access public
     * @return string
     */
    public function getConfigPath(): string
    {
        // 应用根目录/项目根目录
//        return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
        return defined('DATA_PATH')?$this->rootPath.DATA_PATH . 'config' . DIRECTORY_SEPARATOR : $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
    }

    /**
     * 获取配置后缀
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @access public
     * @return string
     */
    public function getConfigExt(): string
    {
        // 配置后缀
        return $this->configExt;
    }

    /**
     * 获取应用开启时间
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @access public
     * @return float
     */
    public function getBeginTime(): float
    {
        // 应用开始时间
        return $this->beginTime;
    }

    /**
     * 获取应用初始内存占用
     * @depreacate 作用:数据
     * @depreacate 调用级别:0 无调用
     *
     * @access public
     * @return integer
     */
    public function getBeginMem(): int
    {
        // 应用内存初始占用
        return $this->beginMem;
    }

    /**
     * 初始化应用
     * @depreacate 作用:逻辑/调度
     * @depreacate 调用级别:1 无上级调用但有下级子调用
     * @todo 如果要做多真正的多应用共享模式.需要对这块进行改造.而且这个,不一定符合开发需要,太简,限制太死
     *
     * @access public
     * @return $this
     */
    public function initialize()
    {
        // 初始化
        $this->initialized = true;

        // 如果是生产环境,这个地方其实可以不加载
        // 应用开始时间
        $this->beginTime = microtime(true);
        // 应用内存初始占用
        $this->beginMem  = memory_get_usage();

        // 这块配置加载的比较散乱.没明白是为什么这么写
        // 加载开发环境变量
        // 如果要对应不同的环境,可以对此处进行调整,以方便加载不同环境配置文件
        // 后期考虑,可以加载不同环境参数
        if (is_file($this->rootPath . '.env')) {
            $this->env->load($this->rootPath . '.env');
        }

        // 获取配置后缀
        $this->configExt = $this->env->get('config_ext', '.php');

        // 调试模式设置
        $this->debugModeInit();

        //====以上为全局环境限定====

        // 加载全局初始化文件
        // 加载应用文件和配置
        $this->load();

        // 加载框架默认语言包
        $langSet = $this->lang->defaultLangSet();

        $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php');

        // 加载应用默认语言包
        $this->loadLangPack($langSet);

        // 监听AppInit,默认是空的无事件出来
        $this->event->trigger(AppInit::class);

        // 设置时区
        date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai'));


        // 初始化应用初始化器
        // 把涉及到的类都初始一次并绑定到本容器中
        foreach ($this->initializers as $initializer) {
            $this->make($initializer)->init($this);
        }

        return $this;
    }

    /**
     * 是否初始化过
     * @depreacate 作用:数据
     * @depreacate 调用级别:3 有上级调用但无下级调用
     *
     * @return bool
     */
    public function initialized()
    {
        // 初始化
        return $this->initialized;
    }

    /**
     * 加载语言包
     * @depreacate 作用:逻辑/调度
     * @depreacate 调用级别:3 有上级调用但无下级调用
     *
     * @param string $langset 语言
     * @return void
     */
    public function loadLangPack($langset)
    {
        if (empty($langset)) {
            return;
        }

        // 加载系统语言包
        $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
        $this->lang->load($files);

        // 加载扩展（自定义）语言包
        $list = $this->config->get('lang.extend_list', []);

        if (isset($list[$langset])) {
            $this->lang->load($list[$langset]);
        }
    }

    /**
     * 引导应用
     * @depreacate 作用:逻辑/调度
     * @depreacate 调用级别:1 无上级调用但有下级子调用
     *
     * @access public
     * @return void
     */
    public function boot(): void
    {
        // 使用用户自定义函数对数组中的每个元素做回调处理
        // 将用户自定义函数 funcname 应用到 array 数组中的每个单元
        // 注册的系统服务
        array_walk($this->services, function ($service) {
            // 执行服务
            $this->bootService($service);
        });
    }

    /**
     * 加载应用文件和配置
     * @depreacate 作用:逻辑,数据
     * @depreacate 调用级别:2 有上级调用也有下级调用
     * @todo 加载应用目录下的一些文件,但这个地方的加载也是有点相同聚类的原则,有点混乱.其实就是职责不清
     *
     * @access protected
     * @return void
     */
    protected function load(): void
    {
        // 获取当前应用目录,加载时,已经加载过了
        $appPath = $this->getAppPath();

        // 加载 应用公共文件
        // 这里的公共文件其实就一个函数集文件.过程文件.在其它地方需要快捷调用的方法 类似 helper.php
        // 需要考虑多级模块,下的目录调用问题
        if (is_file($appPath . 'common.php')) {
            include_once $appPath . 'common.php';
        }

        // 加载框架的助手文件
        include_once $this->thinkPath . 'helper.php';

        // 获取应用配置目录
        // 这个地方,默认是单应用,所以直接用的 config 目录.但如果是多应用,微服务模式的.这个地方,实际是就是全项目的全局配置
        // 这个地方也是需要处理的
        // 可以加载生成后的临时文件,这样减少IO 损耗
        $configPath = $this->getConfigPath();
        $configGlobalPath = $this->env->get('CONF_ROOT')?$configPath .$this->env->get('CONF_ROOT').DIRECTORY_SEPARATOR:$configPath;
        $configEevPath = $this->env->get('APP_ENV')?$configPath .(defined('APP_NAME')?APP_NAME.DIRECTORY_SEPARATOR:'').$this->env->get('APP_ENV').DIRECTORY_SEPARATOR:'';

        $files = [];
        // 获取产品配置目录下的所有配置文件
        // 注意这个地方不会读取子目录配置文件
        // 这个地方,需要重点改造
        if (is_dir($configGlobalPath)) {
            $files = glob($configGlobalPath . '*' . $this->configExt);
            foreach ($files as $file) {
                $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
            }
        }

        // 根据环境变量判断载入哪个环境应用特定配置
        $files = [];
        // 获取产品配置目录下的所有配置文件
        // 注意这个地方不会读取子目录配置文件
        // 这个地方,需要重点改造
        if (is_dir($configEevPath)) {
            $files = glob($configEevPath . '*' . $this->configExt);
            foreach ($files as $file) {
                $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
            }
        }

        // 加载应用下的事件定义文件
        if (is_file($appPath . 'event.php')) {
            // 注册应用事件
            $this->loadEvent(include $appPath . 'event.php');
        }

        // 加载应用下的服务定义文件
        if (is_file($appPath . 'service.php')) {
            $services = include $appPath . 'service.php';
            foreach ($services as $service) {
                // 注册服务
                $this->register($service);
            }
        }
    }

    /**
     * 调试模式设置
     * @depreacate 作用:实现,数据
     * @depreacate 调用级别:2 有上级调用也有下级调用
     * @todo
     *
     * @access protected
     * @return void
     */
    protected function debugModeInit(): void
    {
        // 应用调试模式
        if (!$this->appDebug) {
            $this->appDebug = $this->env->get('app_debug') ? true : false;
            ini_set('display_errors', 'Off');
        }

        // 是否运行在命令行下
        // 非 cli 下 申请一大块缓存,准备缓写数据
        if (!$this->runningInConsole()) {
            // 重新申请一块比较大的buffer
            if (ob_get_level() > 0) {
                $output = ob_get_clean();
            }
            ob_start();
            if (!empty($output)) {
                echo $output;
            }
        }
    }

    /**
     * 注册应用事件
     * @depreacate 作用:调度,逻辑
     * @depreacate 调用级别:3 有上级调用但无下级调用
     *
     * @access protected
     * @param array $event 事件数据
     * @return void
     */
    public function loadEvent(array $event): void
    {
        if (isset($event['bind'])) {
            // 指定事件别名标识 便于调用
            $this->event->bind($event['bind']);
        }

        if (isset($event['listen'])) {
            // 批量注册事件监听
            // 监听事件注册,就是建立一个事件别名-订阅者的映射关系
            $this->event->listenEvents($event['listen']);
        }

        if (isset($event['subscribe'])) {
            // 注册事件订阅者 什么是订阅者
            // 订阅者就是要执行的方法
            $this->event->subscribe($event['subscribe']);
        }
    }

    /**
     * 解析应用类的类名
     * @depreacate 作用:数据/实现
     * @depreacate 调用级别:0 无调用
     * @todo 注意,这里是应用下的类名,如果不是应用下的.需要注意
     *
     * @access public
     * @param string $layer 层名 controller model ...
     * @param string $name  类名
     * @return string
     */
    public function parseClass(string $layer, string $name): string
    {
        // 统一格式化
        $name  = str_replace(['/', '.'], '\\', $name);
        $array = explode('\\', $name);
        // 下划线转驼峰(首字母大写)
        $class = Str::studly(array_pop($array));
        $path  = $array ? implode('\\', $array) . '\\' : '';


        if(!empty($this->module)){
            return $this->namespace . '\\' .$this->module . '\\' . $layer . '\\' . $path . $class;
        }else{
            return $this->namespace . '\\' . $layer . '\\' . $path . $class;
        }

    }

    /**
     * 是否运行在命令行下
     * @depreacate 作用:逻辑
     * @depreacate 调用级别:3 有上级调用但无下级调用
     *
     * @return bool
     */
    public function runningInConsole()
    {
        return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
    }

    /**
     * 获取应用根目录/项目根目录
     * @depreacate 作用:逻辑/数据
     * @depreacate 调用级别:3 有上级调用但无下级调用
     * @todo 通过定义自身来推导出项目的根目录.但这个地方.也可能不对,如果不遵循框架默认规范,进行了休息,这个地方就会出错
     *
     * @access protected
     * @return string
     */
    protected function getDefaultRootPath(): string
    {
		// 通过相对目录找到项目根目
        return dirname($this->thinkPath, 4) . DIRECTORY_SEPARATOR;
    }


    public function setModule(string $name): void
    {
        $this->module = $name;
    }


    public function getModule(): string
    {
        return $this->module;
    }

}
